package IModule;

#
# moduleClasses - это классы которые необходимо уметь создовать, т.к. они используются
# в работе IOC контейнера
# providers - то что модуль предоставляет
#

use Set;
use Signature;
use File::Spec;

use TinyMessage;

sub new {
	my ($class) = @_;

	my $self = {
		class           => undef,
		providers       => [],
		moduleProviders => [],
		moduleClasses   => new Set(),
		includes        => {},
		project         => undef,
		ioc_variable    => undef
	};

	bless $self, $class;

	return $self;
}

sub addProvider {
	my ( $this, $p ) = @_;
	push( @{ $this->{providers} }, $p );
}

sub addModuleProvider {
	my ( $this, $p ) = @_;
	push( @{ $this->{moduleProviders} }, $p );
}

sub includes {
	my ( $this, $out ) = @_;
	my %includes =
	  map { length > 0 ? ( $_, 1 ) : () } keys( %{ $this->{includes} } );

	foreach my $moduleProvider ( @{ $this->{moduleProviders} } ) {
		my $module = $this->{project}->module( $moduleProvider->module_sig() );
		if ($module) {
			my %moduleIncludes = $module->includes();
			%includes = ( %includes, %moduleIncludes );
		}
		else {
			fatal 'F_MODULE_NOT_FOUND', $moduleProvider->module_sig();
		}
	}

	return %includes;
}

sub includes_rel {
	my ( $this, $out ) = @_;

	my %includes = $this->includes();
	my @includes = keys(%includes);

	grep { $_ = File::Spec->abs2rel( $_, $out ); } @includes;

	@includes = sort(@includes);

	return \@includes;
}

sub project {
	return (shift)->{project};
}

sub providers {
	my ( $this, $type ) = @_;

	if ($type) {
		my @filtred = ();

		foreach my $provider ( @{ $this->{providers} } ) {
			if ( $provider->type() eq $type ) {
				push @filtred, $provider;
			}
		}

		return \@filtred;
	}
	else {
		return $this->{providers};
	}
}

sub moduleClasses {
	return (shift)->{moduleClasses};
}

sub setProject {
	my ( $module, $project ) = @_;
	$module->{project} = $project;
}

sub begin {
	my ( $module, $project ) = @_;

	my $knownClasses  = $project->classes();
	my $moduleClasses = $module->moduleClasses();

	foreach my $provider ( @{ $module->{providers} } ) {
		if ( $provider->type() eq 's' ) {
			my $impl_sig = signature( $provider->impl_sig() );

			my $class = shouldGet( $knownClasses, $impl_sig );
			my $provider_sig = class_sig( "SINGLETON< $impl_sig >", [] );
			$provider->setSignature($provider_sig);

			$moduleClasses->put( $class->clone() );
		}
		elsif ( $provider->type() eq 'd' ) {
			my $impl_sig = signature( $provider->impl_sig() );

			my $class = shouldGet( $knownClasses, $impl_sig );
			my $provider_sig = class_sig( "DIRECT< $impl_sig >", [] );
			$provider->setSignature($provider_sig);

			$moduleClasses->put( $class->clone() );
		}
		else {
			my $class = shouldGet( $knownClasses, $provider->impl_sig() );

			$provider->setSignature( $class->{sig} );

			$module->{includes}->{ $class->{file} } = 1;
		}
	}
}

sub commit {
	my ($this) = @_;

	# calculate full include list
	$this->{includes}->{ $this->{class}->file() } = 1;
	foreach my $class ( $this->{moduleClasses}->values() ) {
		$this->{includes}->{ $class->file() } = 1 if ( $class->file() );
	}
}

sub setClass {
	my ( $this, $class ) = @_;
	$this->{class} = $class;
}

sub class {
	return (shift)->{class};
}

sub setIOCVariable {
	my ( $t, $v ) = @_;
	$t->{ioc_variable} = $v;
}

sub variable {
	return (shift)->{ioc_variable};
}

sub spawnCreators {
	my ( $module, $factory ) = @_;

	foreach my $class ( $module->moduleClasses()->values() ) {
		if ( !$factory->spawnCreator( $class, $module ) ) {
			fatal 'F_NO_MATCHED_CONSTUCTOR', $class, $module;
		}
	}
}

#retrun provider of class, or undef
sub class_provider {
	my ( $this, $sig ) = ( shift, shift );

	foreach my $provider ( @{ $this->{providers} } ) {
		if ( sigs_eq( $provider->class_sig(), $sig ) ) {
			return $provider->method();
		}
	}

	#try luck with sub modules
	my @subModules = $this->subModules();
	
	foreach ( @subModules ) {
		my $module = $_->{module};
		my $moduleProvider = $_->{provider};
		my $provider = $module->class_provider($sig);

		next if ( !$provider );

		if ( ref($provider) ) {
			return $moduleProvider->method() . "()->" . $provider->method();
		}
		else {
			return $moduleProvider->method() . "()->" . $provider;
		}
	}

	return 0;
}

sub subModules {
	my ($this) = shift;
	
	my @modules = ();
	foreach my $moduleProvider ( @{ $this->{moduleProviders} } ) {
		my $module = $this->{project}->module( $moduleProvider->module_sig() );
		fatal 'F_MODULE_NOT_FOUND', $moduleProvider->module_sig() if ( !$module );
		
		push @modules, {module => $module, provider => $moduleProvider};
	}
	
	return @modules;
}

#retrun class creator or die
sub class_creator {
	my ( $module, $cclass ) = @_;

	return shouldGet( $module->moduleClasses(), $cclass )->{creator};
}

1;
